/**
 * Copyright (c) 2010-2011 Pierre Labatut
 *
 * This file is part of Space-Chronicles project.
 *
 * Space-Chronicles is free software: you can redistribute it and/or modify it
 * under the terms of its license which may be found in the accompanying
 * LICENSE file or <http://code.google.com/p/space-chronicles/>.
 */
package space.model;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.List;
import javax.media.opengl.GL;
import javax.media.opengl.GL3;
import javax.media.opengl.GL3bc;
import space.gl.Attribute;
import space.gl.RenderContext;
import space.stream.StructureInput;

/**
 *
 * @author Pierre
 */
public class MdlMeshPack implements Drawable {

    final String name;
    final ByteBuffer packedData;
    final private static int STRIDE = 14 * 4;
    final private static int TEXTURE_OFFSET = 0;
    final private static int TANGENT_OFFSET = 2 * 4;
    final private static int BINORMAL_OFFSET = 5 * 4;
    final private static int NORMAL_OFFSET = 8 * 4;
    final private static int VERTEX_OFFSET = 11 * 4;
    final ByteBuffer triangleIndex;

    MdlMeshPack(String name, ByteBuffer vertex_T2F_TBN9F_V3F, ByteBuffer triangleIndex) {
        if (name == null) {
            throw new IllegalArgumentException();
        }
        if (vertex_T2F_TBN9F_V3F == null) {
            throw new IllegalArgumentException();
        }
        if (triangleIndex == null) {
            throw new IllegalArgumentException();
        }

        this.name = name;
        this.packedData = vertex_T2F_TBN9F_V3F;
        this.triangleIndex = triangleIndex;

        validateStructure();
    }

    public MdlMeshPack(StructureInput si) {
        name = si.readString();
        packedData = si.readByteBuffer();
        triangleIndex = si.readByteBuffer();

        validateStructure();
    }

    private void validateStructure() {
        if (name == null) {
            throw new IllegalArgumentException();
        }
        if (packedData == null) {
            throw new IllegalArgumentException();
        }
        if (packedData.remaining() % (14 * 4) != 0) {
            throw new IllegalArgumentException(
                    "Packed vertex data size is not a multiple of 14 (s,t,tx,ty,tz,bx,by,bz,nx,ny,nz,x,y,z) x Float size (4)");
        }

        if (packedData.isDirect() != true || packedData.order() != ByteOrder.nativeOrder()) {
            throw new IllegalArgumentException();
        }
        if (packedData.position() != 0 || packedData.limit() != packedData.capacity()) {
            throw new IllegalArgumentException("Packed vertex data does not fit the buffer size");
        }
        if (triangleIndex == null) {
            throw new IllegalArgumentException();
        }
        if (triangleIndex.remaining() % (3 * 4) != 0) {
            throw new IllegalArgumentException();
        }
        if (triangleIndex.isDirect() != true || triangleIndex.order() != ByteOrder.nativeOrder()) {
            throw new IllegalArgumentException();
        }
        if (triangleIndex.position() != 0 || triangleIndex.limit() != triangleIndex.capacity()) {
            throw new IllegalArgumentException();
        }

    }

    public void draw(RenderContext rc) {
        rc.applyAllUniforms();
        final GL3bc gl = rc.gl();

        //Disable element vertex_buffer_object
        gl.glBindBuffer(GL3.GL_ELEMENT_ARRAY_BUFFER, 0);
        gl.glBindBuffer(GL3.GL_ARRAY_BUFFER, 0);


        packedData.position(TEXTURE_OFFSET);
        rc.applyVertexAttribPointer(Attribute.UV, 2, GL.GL_FLOAT, false, STRIDE, packedData);

        packedData.position(TANGENT_OFFSET);
        rc.applyVertexAttribPointer(Attribute.TANGENT, 3, GL.GL_FLOAT, false, STRIDE, packedData);

        packedData.position(BINORMAL_OFFSET);
        rc.applyVertexAttribPointer(Attribute.BINORMAL, 3, GL.GL_FLOAT, false, STRIDE, packedData);

        packedData.position(NORMAL_OFFSET);
        rc.applyVertexAttribPointer(Attribute.NORMAL, 3, GL.GL_FLOAT, false, STRIDE, packedData);

        packedData.position(VERTEX_OFFSET);
        rc.applyVertexAttribPointer(Attribute.VERTEX, 3, GL.GL_FLOAT, false, STRIDE, packedData);

        packedData.position(0);

        //Immediate draw
        gl.glDrawElements(GL3bc.GL_TRIANGLES, triangleIndex.capacity() * Byte.SIZE / Integer.SIZE,
                GL3bc.GL_UNSIGNED_INT, triangleIndex);
        rc.glDisableVertexAttribArray(Attribute.UV);
        rc.glDisableVertexAttribArray(Attribute.TANGENT);
        rc.glDisableVertexAttribArray(Attribute.BINORMAL);
        rc.glDisableVertexAttribArray(Attribute.NORMAL);
        rc.glDisableVertexAttribArray(Attribute.VERTEX);


    }

    private static int genBuffer(GL3bc gl) {
        int buffers[] = new int[1];
        gl.glGenBuffers(buffers.length, buffers, 0);
        return buffers[0];
    }

    private static int genVertexArray(GL3bc gl) {
        final int tVertexArray[] = new int[1];
        gl.glGenVertexArrays(tVertexArray.length, tVertexArray, 0);
        return tVertexArray[0];

    }

    private static int[] toIntArray(List<Integer> list) {
        int[] ret = new int[list.size()];
        for (int i = 0; i < ret.length; i++) {
            ret[i] = list.get(i);
        }
        return ret;
    }

    public MeshVertexArray optimize(GL3bc gl) {
        /**
         * Build Buffers
         */
        final List<MeshVertexArrayDrawElement> vades = new ArrayList<MeshVertexArrayDrawElement>();
        final List<Integer> buffers = new ArrayList<Integer>();
        {
            // Build Triangle Vertex Array
            int tVertexArray = genVertexArray(gl);
            gl.glBindVertexArray(tVertexArray);
            {
                // Build Vertex Buffer
                final int vertexBufferId = genBuffer(gl);
                gl.glBindBuffer(GL3.GL_ARRAY_BUFFER, vertexBufferId);
                gl.glBufferData(GL3.GL_ARRAY_BUFFER, packedData.capacity(), packedData, GL3.GL_STATIC_DRAW);


                gl.glVertexAttribPointer(Attribute.UV, 2, GL.GL_FLOAT, false, STRIDE, TEXTURE_OFFSET);
                gl.glEnableVertexAttribArray(Attribute.UV);
                gl.glVertexAttribPointer(Attribute.TANGENT, 3, GL.GL_FLOAT, false, STRIDE, TANGENT_OFFSET);
                gl.glEnableVertexAttribArray(Attribute.TANGENT);

                gl.glVertexAttribPointer(Attribute.BINORMAL, 3, GL.GL_FLOAT, false, STRIDE, BINORMAL_OFFSET);
                gl.glEnableVertexAttribArray(Attribute.BINORMAL);

                gl.glVertexAttribPointer(Attribute.NORMAL, 3, GL.GL_FLOAT, false, STRIDE, NORMAL_OFFSET);
                gl.glEnableVertexAttribArray(Attribute.NORMAL);

                gl.glVertexAttribPointer(Attribute.VERTEX, 3, GL.GL_FLOAT, false, STRIDE, VERTEX_OFFSET);
                gl.glEnableVertexAttribArray(Attribute.VERTEX);


                // Build Triangle Buffer
                int triangleBufferId = genBuffer(gl);
                gl.glBindBuffer(GL3.GL_ELEMENT_ARRAY_BUFFER, triangleBufferId);
                gl.glBufferData(GL3.GL_ELEMENT_ARRAY_BUFFER, triangleIndex.capacity(), triangleIndex, GL3.GL_STATIC_DRAW);

                vades.add(new MeshVertexArrayDrawElement(tVertexArray, GL3bc.GL_TRIANGLES, triangleIndex.capacity() / 4, GL3bc.GL_UNSIGNED_INT));
                buffers.add(vertexBufferId);
                buffers.add(triangleBufferId);
            }
            gl.glBindVertexArray(0);
            gl.glBindBuffer(GL3.GL_ARRAY_BUFFER, 0);
            gl.glBindBuffer(GL3.GL_ELEMENT_ARRAY_BUFFER, 0);
        }

        return new MeshVertexArray(toIntArray(buffers),
                vades.toArray(new MeshVertexArrayDrawElement[vades.size()]));
    }

    public void dispose(GL3bc gl) {
    }
}
